function [ BestPoint, BestObjective, PointHistory, PointObjectiveHistory, func_evals ] = ...
    BinaryTabuMonteCarlo_PointLimit( ObjectiveFunction, Dims, MaxSingleChangeTrials, TabuOnOff, PointLimit )
% This optimization function solves a binary optimization problem for a
% single objective.
% Search for "Crow" for changes by line
% Inputs:
% - Objective Function: The objective function which uses the binary
% input
% - Dims: The length of the binary vector which is passed to the
% objective function
% - MaxSingleChangeTrials: The number of times the algorithm searches a
% better point in its direct surrounding before the algorithm
% terminates
% - TabuOnOff: Whether the tabu search component should be switched on or
% off
% - PointLimit: Max number of Dims that can be set to 1
    

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Calculate initial point and its objective
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Create random binary starting point
Point = zeros(Dims,1); % Initiate point
idx = randperm(Dims); 
% idx = idx( 1:randi(Dims,1) ); % Create a random set of indices which should be set to 1 (commented out by Crow)
% idx = idx( 1:randi(PointLimit,1) ); % pull up to PointLimit number (Crow)
idx = idx(1:PointLimit); %pull exactly PointLimit number (Crow)

Point( idx ) = 1;
% Calculate its objective
PointObjective = ObjectiveFunction(Point); func_evals = 1;
    
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Initiate the histories
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
PointHistory = Point;
PointObjectiveHistory = PointObjective;

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Initiate the tabu list
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
if TabuOnOff == 1; TabuList = []; end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Main algorithm: Try to find a better value in the immediate
% surrounding
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
NumberOfSingleSwitches = 0;
while NumberOfSingleSwitches <= MaxSingleChangeTrials

    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    % Change one random point value
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    % Choose a random index
    idx = randi(length(Point),1);
    % Choose a different one if it is already present in the TabuList
        if TabuOnOff == 1
            while ~isempty(find(TabuList==idx))
                idx = randi(length(Point),1);
            end
            % Note the point down in the tabu list
            TabuList(end+1) = idx;
        end
    % Create the new point and calculate its objective
    NewPoint = BinaryChangePoint( Point, idx );
    tmp1=randperm(length(idx)); tmp2=idx(tmp1(1)); NewPoint(tmp2)=0; %randomly switch one point to 0 to maintain PointLimit (Crow)
    NewPointObjective = ObjectiveFunction(NewPoint); func_evals = func_evals + 1;
    
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    % Selection
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    if NewPointObjective < PointObjective
        % Replace current point
        Point = NewPoint;
        PointObjective = NewPointObjective;
        % Fill new point in history
        PointHistory(:,end+1) = NewPoint;
        PointObjectiveHistory(:,end+1) = NewPointObjective;
        % Reset the number of switches
        NumberOfSingleSwitches = 0;
    end
    
    % Break algorithm if TabuList is full
    if TabuOnOff == 1 && length(TabuList) == Dims
        break
    end
    
    NumberOfSingleSwitches = NumberOfSingleSwitches + 1;
  
end
    
BestPoint = Point;
BestObjective = PointObjective;
    
end % function
 
function Point = BinaryChangePoint( Point, idx )
    % This sub function switches an index subset of points from 0 to 1 and vice versa
    for i = 1:length(idx)
        if Point(idx(i)) == 1
            Point(idx(i)) = 0;
        elseif Point(idx(i)) == 0
            Point(idx(i)) = 1;
        else
            error('Point should only have 0 1nd 1 as entries!')
        end
    end
end 